package com.espirit.eap.sa;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;

import com.googlecode.cswish.exception.ApplicationException;
import com.googlecode.cswish.model.PageElements;
import com.googlecode.cswish.model.PropertyInfo;
import com.googlecode.cswish.struts.Invoker;
import com.googlecode.cswish.struts.Routing;
import com.googlecode.cswish.struts.conversion.PageElementService;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.CacheService;
import com.googlecode.cswish.struts.spring.Executor;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.util.FrameConstant;
import com.googlecode.cswish.util.FreemarkerTplEngine;
import com.googlecode.cswish.util.Functions;
import com.googlecode.cswish.util.InvocationUtil;
import com.googlecode.cswish.util.bean.ModelUtil;
import com.opensymphony.xwork2.ActionContext;

@Service
public class ValidationService {

	private final static Logger logger = Logger.getLogger(ValidationService.class);
	
	@Resource
	private GenericService genericService;
	
	@Resource
    private Functions functions;
	
	@Resource
    private BeanIntrospector beanIntrospector;
	
	@Resource
    private InvocationUtil invocationUtil;
	
	@Resource
	private CacheService cacheService;
	
	@Resource
	private PageElementService pageElementService;
	
	@Resource
	private Routing routing;
	
	private Map<String, List<Validation>> validationCache;
	
	
	private void addValidation(Validation valid) {
		if (valid.getEnable() == null || !valid.getEnable()) {
			return;
		}
		
		List<Validation> validations = validationCache.get(valid.getAction());
		if (validations == null) {
			validations = new ArrayList<Validation>(1);
			validationCache.put(valid.getAction(), validations);
		}
		validations.add(valid);
	}
	
	private void removeValidation(Validation valid) {
		if (valid == null) return ;
		List<Validation> validations = validationCache.get(valid.getAction());
		if (validations != null) {
			Integer id = valid.getId();
			for (int i = 0, len = validations.size(); i < len; i++) {
				Validation validation = validations.get(i);
				if (validation.getId().equals(id)) {
					validations.remove(i);
					
					Invoker invoker = routing.action2Invoker(validation.getAction());
					String simpleName = beanIntrospector.getSimpleName(invoker.actionName);
					pageElementService.reset(simpleName, invoker.methodName);
					break;
				}
			}
		}
	}
	
	public void validate(String actionName, Map action) {		
		
		Map<String, List<Validation>> validCache = validationCache;
		if (validCache == null) {
			synchronized (this) {
				if (validationCache == null) {
					validationCache = (Map<String, List<Validation>>)cacheService.newCaches(Validation.class);
					validCache = validationCache;
					
					Validation validation = new Validation();
					validation.setEnable(Boolean.TRUE);
					List<Validation> validations = genericService.searchAll(validation).getData();
					for (Validation valid : validations) {
						addValidation(valid);
					}
					
					genericService.on(Validation.class, "merge,delete", new Executor() {
						@Override
						public Object execute(Object parameter) {
							Object[] parameters = (Object[])parameter;
							Validation newDbModel = (Validation) parameters[0];
							Validation oldDbModel = (Validation) parameters[1];
							String operation = (String)parameters[2];
							if (validationCache != null) {
								removeValidation(oldDbModel);
								if (!"delete".equals(operation)) {
									addValidation(newDbModel);
								}
							}
							return null;
						}
					}, false);
				}
			}
		}
		
		
		List<Validation> validations = validCache == null ? null : validCache.get(actionName);
		if (validations != null && validations.size() > 0) {	
			PageElements userElements = (PageElements) ActionContext.getContext().get(FrameConstant.STACK_PAGEELEMENTS);
			Map<String, Object> validationParameters = new HashMap<String, Object>(action);
			invocationUtil.forceLoadModelAndResults(validationParameters);
			validationParameters.put("fn", functions);
			validationParameters.put("ps", userElements);
			
			for (Validation validation : validations) {
				Map<String, Object> traceResult = new HashMap<String, Object>(4);
				traceResult.put("exception", null);
				traceResult.put("arguments", null);
				
				Collection validateColl;
				if (validation.getField() != null) {
					Object value = ModelUtil.getProperty(validationParameters, validation.getField());
					if (value instanceof Collection) {
						boolean isListField = false;
						int index = validation.getField().indexOf('.');			// skip the first 'dbmodel.', 'dbresult.'
						if (index > 0) {
							String simpleName = beanIntrospector.getSimpleName(actionName);
							String modelName = beanIntrospector.getModelName(simpleName);
							String nestedProperty = validation.getField().substring(index + 1);
							PropertyInfo propertyInfo = beanIntrospector.getPropertyInfo(modelName, nestedProperty );
							if (propertyInfo != null) {
								Class clazz = propertyInfo.getTypeClass();
								isListField = propertyInfo.isSimpleObj() && List.class.isAnnotationPresent(clazz);
							}
						}
						if (isListField) {
							validateColl = new ArrayList(1);
							validateColl.add(value);
						} else {
							validateColl = (Collection) value;
						}
					} else {
						validateColl = new ArrayList(1);
						validateColl.add(value);
					}
				} else {
					validateColl = new ArrayList(1);
					validateColl.add(action);
				}
				
				for (Object validateValue : validateColl) {
					validationParameters.put("this", validateValue);
					validateIt(validationParameters, validation, traceResult);
				}
			}
		}
	}

	private void validateIt(Map<String, Object> validationParameters,
			Validation validation, Map<String, Object> traceResult) {
		FreemarkerTplEngine.getInstance().execute(validation.getValidCondition(), validationParameters, traceResult);
		
		String exception = (String) traceResult.get("exception");
		if (exception != null) {
			ApplicationException ex = new ApplicationException(exception);
			Object[] arguments = (Object[]) traceResult.get("arguments");
			ex.setArguments(arguments);
			throw ex;
		}
	}
}